/*
 * Copyright (c) 2012 Tomas Shestakov. <http://code.google.com/p/jmmo/>
 */

package jmmo.engine.property

import jmmo.util.ValueWrapperClass
import reflect.ClassTag

/**
 * Serve for object identification.
 * Contains a map with tuples (attribute name -> attribute value).
 * Has a special `name` attribute of the form (Identifier.nameSymbol: Symbol -> name: String)
 */
trait Identifier extends Equals with Serializable {

  /**
   * Returns attribute value with specified name and type wrapped in Option
   * or None if attribute is not exists or type is not conform.
   * @param name the attribute name
   * @param ct the attribute value class tag
   * @tparam T type of attribute value
   * @return attribute value with specified name and type wrapped in Option
   */
  def attribute[T](name: Any)(implicit ct: ClassTag[T]): Option[T]

  /**
   * @return a Map for object identification contains pairs (attribute name -> attribute value).
   */
  def attributes: scala.collection.Map[Any, Any]

  /**
   * @return `name` String attribute of the Identifier or "" if it is not exists
   */
  def name = attribute[String](Identifier.nameSymbol).getOrElse("")
}

/**
 * For creation instances of `Identifier`
 */
object Identifier {

  /**
   * Symbol denoting the `name` attribute of the `Identifier`
   */
  val nameSymbol = 'name

  /**
   * @return an identifier with no attributes and no values.
   */
  val empty = new IdentifierImpl(Map.empty)

  /**
   * Creates the Identifier with specified attributes
   * @param attributes the Identifier attributes
   * @return new instance implements Identifier trait
   */
  def apply(attributes: (Any, Any)*) = new IdentifierImpl(Map(attributes: _*))

  /**
   * Creates the Identifier with specified name and other attributes
   * @param name the Identifier `name` attribute
   * @param attributes the Identifier attributes
   * @return new instance implements Identifier trait
   */
  def apply(name: String, attributes: (Any, Any)*) = new IdentifierImpl(Map( (nameSymbol -> name) +: attributes: _* ))

  def unapply(identifier: Identifier): Option[scala.collection.Map[Any, Any]] =
    if (identifier eq null) None
    else Some(identifier.attributes)

  private[Identifier] class IdentifierImpl(val attributes: scala.collection.Map[Any, Any]) extends Identifier {

    def attribute[T](name: Any)(implicit ct: ClassTag[T]): Option[T] = attributes.get(name) match {
      case Some(value) if ValueWrapperClass(ct.runtimeClass).isAssignableFrom(ValueWrapperClass(value.asInstanceOf[AnyRef].getClass)) =>
        Some(value.asInstanceOf[T])
      case _ => None
    }

    def canEqual(other: Any) = other.isInstanceOf[Identifier]

    override def equals(other: Any) = other match {
      case that: Identifier => (that canEqual this) && attributes == that.attributes
      case _ => false
    }

    override def hashCode = attributes.hashCode()

    override def toString = "Identifier(" + attributes.mkString(",")  + ")"
  }
}